#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#define DTSLED_CNT 1  // 设备号个数
#define DTSLED_NAME "dtsled" // 名字
#define LEDOFF 				0			/* 关灯 	*/
#define LEDON 				1			/* 开灯 	*/

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI;
static void __iomem *PMU_GRF_GPIO0C_DS_0_PI;
static void __iomem *GPIO0_SWPORT_DR_H_PI;
static void __iomem *GPIO0_SWPORT_DDR_H_PI;

void led_unmap(void){
	/* 取消映射 */
	iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);
	iounmap(PMU_GRF_GPIO0C_DS_0_PI);
	iounmap(GPIO0_SWPORT_DR_H_PI);
	iounmap(GPIO0_SWPORT_DDR_H_PI);
}


/*dtsled设备结构体*/

struct dtsled_dev{
  dev_t devid;  // 设备号
  struct cdev cdev; // 字符设备
  struct class* class; // 类
  struct device* device; // 设备
  int major;  // 主设备号
  int minor; // 次设备号
  struct device_node *nd;  // 设备节点
};

struct dtsled_dev dtsled;  // led设备

void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO0_SWPORT_DR_H_PI);
		val &= ~(0X1 << 0); /* bit0 清零*/
		val |= ((0X1 << 16) | (0X1 << 0));	/* bit16 置1，允许写bit0，
	 					   				   bit0，高电平*/
		writel(val, GPIO0_SWPORT_DR_H_PI);
	}else if(sta == LEDOFF) { 
		val = readl(GPIO0_SWPORT_DR_H_PI);
		val &= ~(0X1 << 0); /* bit0 清零*/
		val |= ((0X1 << 16) | (0X0 << 0));	/* bit16 置1，允许写bit0，
	 					   				   bit0，低电平	*/
		writel(val, GPIO0_SWPORT_DR_H_PI);
	} 
}

static int dtsled_open(struct inode* inode, struct file* filp){
  filp->private_data = &dtsled;
  return 0;
}
static int dtsled_release(struct inode* inode, struct file* filp){
  //struct dtsled_dev* dev = (struct dtsled_dev* )filp->private_data;
  return 0;
}
static ssize_t dtsled_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos){
  //struct dtsled_dev* dev = (struct dtsled_dev* )filp->private_data;
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, count);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);		/* 关闭LED灯 */
	}
	return 0;

}


// dtsled 字符设备操作集
static const struct file_operations dtsled_fops = {
  .owner = THIS_MODULE,
  .write = dtsled_write,
  .open = dtsled_open,
  .release = dtsled_release,
};



/*入口函数*/

static int __init dtsled_init(void){
  int ret = 0;
  const char* str;
  u32 regdata[16];
  int i = 0;
  unsigned int val = 0;

  // 注册字符设备，新的开发方式
  dtsled.major = 0; // 设备号由内核分配
  if(dtsled.major){ // 定义了设备号
    dtsled.devid = MKDEV(dtsled.major, 0);
    ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
  }else{ // 没有给定设备号
    ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
    dtsled.major = MAJOR(dtsled.devid);
    dtsled.minor = MINOR(dtsled.minor);
  }

  if(ret < 0){
    printk("dtsled init error \r\n");
    return -1;
  }

  // 添加字符设备
  dtsled.cdev.owner = THIS_MODULE;
  cdev_init(&dtsled.cdev, &dtsled_fops);  // 初始化
  ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT); // 添加到内核
  if(ret < 0){
    printk("dtsled add failed \r\n");
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT); // 释放设备号
    return -1;
  }

  // 自动创建设备节点


  dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);  // 创建类
  if(IS_ERR(dtsled.class)){
    ret= PTR_ERR(dtsled.class);

    cdev_del(&dtsled.cdev); // 删除设备
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT); // 释放设备号
    return -1;
  }

  dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
  if(IS_ERR(dtsled.device)){
    ret = PTR_ERR(dtsled.device);

    cdev_del(&dtsled.cdev); // 删除设备
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT); // 释放设备号
    class_destroy(dtsled.class); // 删除类

    return -1;
  }

  /*获取设备树属性函数*/
  dtsled.nd = of_find_node_by_path("/alphaled");
  if(dtsled.nd == NULL){
    ret = -EINVAL;
    printk("find node by path error\r\n");
    return -1;
  }

  ret = of_property_read_string(dtsled.nd, "status", &str);
  if(ret < 0){
    printk("get status failed!\r\n");
  }else{
    printk("status= %s \r\n", str);
  }

  ret = of_property_read_string(dtsled.nd, "compatible", &str);
  if(ret < 0){
    printk("get compatible failed!\r\n");
  }else{
    printk("compatible= %s \r\n", str);
  }

  ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 16);
  if(ret < 0){
    printk("read reg error \r\n");
    cdev_del(&dtsled.cdev); // 删除字符设备
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT); // 删除设备号 
    device_destroy(dtsled.class, dtsled.devid); // 删除设备 （设备用到类）
    class_destroy(dtsled.class); // 删除类
    return -1;
  }else{
    printk("reg data: \r\n");
    for(i = 0; i < 16; i ++){
      printk("%#X ", regdata[i]);
    }
    printk("\r\n");

  }

  /* LED灯初始化 */ 
  // 内存映射
	PMU_GRF_GPIO0C_IOMUX_L_PI = of_iomap(dtsled.nd, 0);
  PMU_GRF_GPIO0C_DS_0_PI = of_iomap(dtsled.nd, 1);
	GPIO0_SWPORT_DR_H_PI = of_iomap(dtsled.nd, 2);
	GPIO0_SWPORT_DDR_H_PI = of_iomap(dtsled.nd, 3);
  // 初始化io
	/* 2、设置GPIO0_C0为GPIO功能。*/
	val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);
	val &= ~(0X7 << 0);	/* bit2:0，清零 */
	val |= ((0X7 << 16) | (0X0 << 0));	/* bit18:16 置1，允许写bit2:0，
	 					   				   bit2:0：0，用作GPIO0_C0	*/
	writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);

	/* 3、设置GPIO0_C0驱动能力为level5 */
	val = readl(PMU_GRF_GPIO0C_DS_0_PI);
	val &= ~(0X3F << 0);	/* bit5:0清零*/
	val |= ((0X3F << 16) | (0X3F << 0));	/* bit21:16 置1，允许写bit5:0，
	 					   				   bit5:0：0，用作GPIO0_C0	*/
	writel(val, PMU_GRF_GPIO0C_DS_0_PI);

	/* 4、设置GPIO0_C0为输出 */
	val = readl(GPIO0_SWPORT_DDR_H_PI);
	val &= ~(0X1 << 0); /* bit0 清零*/
	val |= ((0X1 << 16) | (0X1 << 0));	/* bit16 置1，允许写bit0，
	 					   				   bit0，高电平	*/
	writel(val, GPIO0_SWPORT_DDR_H_PI);

	/* 5、设置GPIO0_C0为低电平，关闭LED灯。*/
	val = readl(GPIO0_SWPORT_DR_H_PI);
	val &= ~(0X1 << 0); /* bit0 清零*/
	val |= ((0X1 << 16) | (0X0 << 0));	/* bit16 置1，允许写bit0，
	 					   				   bit0，低电平	*/
	writel(val, GPIO0_SWPORT_DR_H_PI);


  return 0;
}

/*出口*/

static void __exit dtsled_exit(void){
   led_switch(LEDOFF);
   led_unmap(); // 取消地址映射
   cdev_del(&dtsled.cdev); // 删除字符设备
   unregister_chrdev_region(dtsled.devid, DTSLED_CNT); // 删除设备号 


   device_destroy(dtsled.class, dtsled.devid); // 删除设备 （设备用到类）
   class_destroy(dtsled.class); // 删除类
}


/* 注册驱动和卸载驱动 */

module_init(dtsled_init);

module_exit(dtsled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Narnat");